﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Dynamic;
using Models;
using Models.Extensions;
using Views;

namespace Presenters
{
    /// <summary>
    /// Presenter for a query tab
    /// </summary>
    public class QueryTabPresenter
        : PresenterBase<IQueryView>
    {
        /// <summary>
        /// Initialise the QueryTab presenter
        /// </summary>
        /// <param name="view">An instance that impliments IQueryView</param>
        /// <param name="mainWindowPresenter">An instance of a MainWindow presenter that this QueryTab belongs to</param>
        public QueryTabPresenter(IQueryView view, MainWindowPresenter mainWindowPresenter)
            : base(view)
        {
            m_MainWindowPresenter = mainWindowPresenter;

            // Set initial status text
            View.Status = "Ready";
            View.Progress = 0;
        }

        /// <summary>
        /// Run the current query against all databases currently loaded into the list
        /// </summary>
        public void RunQueryAll()
        {
            RunQuery(View.DisplayedDatabases);
        }

        /// <summary>
        /// Run the current query on databases that have been marked as selected
        /// </summary>
        public void RunQuerySelected()
        {
            if (View.DisplayedDatabases != null)
            {
                var selectedDbList = (from db in View.DisplayedDatabases
                                      where db.Selected
                                      select db).ToList();
                RunQuery(selectedDbList);
            }
        }

        /// <summary>
        /// Cancel a query if it is running
        /// </summary>
        public void StopQuery()
        {
            if (m_RunQuery != null)
            {
                // Instruct the background worker thread to stop. The query will finish executing on the current database first
                m_RunQuery.CancelAsync();

                // Update status so the user knows we are stopping
                View.Status = "Stopping. Please wait...";
            }
        }

        /// <summary>
        /// Toggle the "Selected" status of the currently selected database item
        /// </summary>
        public void ToggleSelectedDatabase()
        {
            Database selectedDb = View.SelectedDatabase;
            if (selectedDb != null)
            {
                selectedDb.Selected = !selectedDb.Selected;
            }
        }

        /// <summary>
        /// Select all databases
        /// </summary>
        public void SelectAll()
        {
            if (View.DisplayedDatabases != null)
            {
                foreach (Database db in View.DisplayedDatabases)
                {
                    db.Selected = true;
                }
            }
        }

        /// <summary>
        /// Select only those databases whose query ran successfully
        /// </summary>
        public void SelectSuccess()
        {
            if (View.DisplayedDatabases != null)
            {
                foreach (Database db in View.DisplayedDatabases)
                {
                    db.Selected = (db.Result == null) ? false : (db.Selected = db.Result.QuerySucceeded);
                }
            }
        }

        /// <summary>
        /// Select only those databases whose query failed
        /// </summary>
        public void SelectFailure()
        {
            if (View.DisplayedDatabases != null)
            {
                foreach (Database db in View.DisplayedDatabases)
                {
                    db.Selected = (db.Result == null) ? false : (db.Selected = !db.Result.QuerySucceeded);
                }
            }
        }

        /// <summary>
        /// Invert the current selection
        /// </summary>
        public void InvertSelection()
        {
            if (View.DisplayedDatabases != null)
            {
                foreach (Database db in View.DisplayedDatabases)
                {
                    db.Selected = !db.Selected;
                }
            }
        }

        /// <summary>
        /// Edit the selected database
        /// </summary>
        public void EditSelectedDatabase()
        {
            Database selectedDatabase = View.SelectedDatabase;

            if (selectedDatabase != null)
            {
                ViewManager.ViewFactory.EditDatabase(selectedDatabase, m_MainWindowPresenter.View);
                View.RefreshDataGrid();
            }
        }

        /// <summary>
        /// Add databases to the current Query Tab
        /// </summary>
        public void AddDatabases()
        {
            ViewManager.ViewFactory.AddDatabase(View, m_MainWindowPresenter.View);
            
            if (View.FilterApplied)
            {
                // If a filter is applied then re-apply it
                ApplyDBListFilter();
            }
            else
            {
                // Else, simply refresh the database grid
                View.DisplayedDatabases = View.DatabaseList;
                View.RefreshDataGrid();
            }
        }

        /// <summary>
        /// Remove databases from the current QueryTab
        /// </summary>
        public void RemoveDatabases()
        {
            ViewManager.ViewFactory.RemoveDatabases(View, m_MainWindowPresenter.View);

            // If a filter is applied then re-apply it
            if (View.FilterApplied)
            {
                ApplyDBListFilter();
            }
            else
            {
                // Else, simply refresh the database grid
                View.DisplayedDatabases = View.DatabaseList;
                View.RefreshDataGrid();
            }
        }

        /// <summary>
        /// Apply the database/server name, database type and success filters to the currently loaded database list
        /// </summary>
        public void ApplyDBListFilter()
        {
            string dbName = View.DatabaseNameFilter;
            bool successfull = View.SuccessfullQueryFilter;
            Guid pluginID = View.SelectedPluginID;

            List<Database> filteredDbs = View.DatabaseList;

            if ((filteredDbs != null) && (filteredDbs.Count > 0))
            {

                // Filter by name if required
                if (!string.IsNullOrEmpty(dbName))
                {
                    filteredDbs = (from db in filteredDbs
                                   where db.DatabaseName.Contains(dbName) || db.DatabaseServer.ServerName.Contains(dbName)
                                   select db).ToList();
                }

                // Filter successfull queries if required
                if (successfull)
                {
                    filteredDbs = (from db in filteredDbs
                                   where db.Result != null && db.Result.QuerySucceeded
                                   select db).ToList();
                }

                // Filter by plugin ID if required
                if (pluginID != Guid.Empty)
                {
                    filteredDbs = (from db in filteredDbs
                                   where db.DatabaseServer.ServerID == pluginID
                                   select db).ToList();
                }

                // Finally apply the filter
                View.DisplayedDatabases = filteredDbs;
                View.RefreshDataGrid();

                // Show the (Active) text to say that the filter is applied
                View.FilterApplied = true;
            }
        }

        /// <summary>
        /// Remove the database list filter
        /// </summary>
        public void RemoveDBListFilter()
        {
            View.DisplayedDatabases = View.DatabaseList;
            View.RefreshDataGrid();

            // Reset filter parameters
            View.DatabaseNameFilter = string.Empty;
            View.SelectedPluginID = Guid.Empty;
            View.SuccessfullQueryFilter = false;

            // Hide the (Active) text to say that the filter is now in-active
            View.FilterApplied = false;
        }

        /// <summary>
        /// Populate the list of available database plugins to filter by
        /// </summary>
        public void PopulateAvailablePlugins()
        {
            Dictionary<Guid, string> availablePlugins = new Dictionary<Guid, string>();
            availablePlugins.Add(Guid.Empty, "All database types");
            availablePlugins.AddRange(Services.ServersService.GetServerPluginList());
            View.AvailablePlugins = availablePlugins;
        }

        /// <summary>
        /// Run the current query on the supplied list of databases
        /// </summary>
        /// <param name="dbList">The list of databases to run the query on</param>
        private void RunQuery(List<Database> dbList)
        {
            Query query = View.LoadedQuery;

            if ((dbList != null) && (dbList.Count > 0) && (query != null) && (!string.IsNullOrEmpty(query.QueryText)))
            {
                // We have a valid list and query so we can start the process

                // Disable the editor
                View.EditorEnabled = false;

                // Remove the results from all the selected queries
                foreach (Database db in dbList)
                {
                    db.Result = null;
                }

                // Create a back ground worker to run the process async
                m_RunQuery = new BackgroundWorker();
                m_RunQuery.WorkerReportsProgress = true;
                m_RunQuery.WorkerSupportsCancellation = true;
                m_RunQuery.RunWorkerCompleted += new RunWorkerCompletedEventHandler(runQuery_RunWorkerCompleted);
                m_RunQuery.ProgressChanged += new ProgressChangedEventHandler(runQuery_ProgressChanged);
                m_RunQuery.DoWork += new DoWorkEventHandler(runQuery_DoWork);

                // Store the data in a dynamic object and start the query
                dynamic data = new ExpandoObject();
                data.Query = query;
                data.DatabaseList = dbList;
                m_RunQuery.RunWorkerAsync(data);
            }
        }

        /// <summary>
        /// This event is raised when we start running the query against the list of databases
        /// </summary>
        /// <param name="sender">Who raised the event</param>
        /// <param name="e">Details about the event</param>
        private void runQuery_DoWork(object sender, DoWorkEventArgs e)
        {
            // Get the data out of the argument
            dynamic data = (dynamic)e.Argument;
            List<Database> dbList = data.DatabaseList;
            Query query = data.Query;

            Services.ServersService serverService;

            // Work out our "percentage complete" increment rounded to an integer
            int percentageInc = 100 / dbList.Count;

            int percentageProgress = 0;
            foreach (Database db in dbList)
            {
                // Create the service with the correct provider to run this query on
                serverService = new Services.ServersService(db.DatabaseServer.ServerID);

                if (serverService.IsConnected)
                {
                    // Update our progress
                    percentageProgress += percentageInc;
                    m_RunQuery.ReportProgress(percentageProgress, db);

                    // Loop through the database list and run the query on each one
                    db.Result = serverService.RunQuery(db, query);
                }
                else
                {
                    // No server service could be loaded for the server id associated with this database so return an error
                    string errMsg = string.Format("No plug in with ID \"{0}\" can be loaded for this database. Query aborted", db.DatabaseServer.ServerID);
                    db.Result = new QueryResult(null, errMsg, db.DatabaseId);
                }

                // Have we been cancelled?
                if (m_RunQuery.CancellationPending)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// Event raised just before we start querying another database
        /// </summary>
        /// <param name="sender">Who raised the event</param>
        /// <param name="e">Details about the event</param>
        private void runQuery_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Update the progress as a percentage on the view
            View.Progress = e.ProgressPercentage;

            if (e.UserState is Database)
            {
                // Set the status text to the name of the database we are just about to start running the query on
                Database db = (Database)e.UserState;
                View.Status = string.Format("Running Query on {0}", db.DatabaseName);
            }
        }

        /// <summary>
        /// Event raised when the query is fully completed on our entire database list
        /// </summary>
        /// <param name="sender">Who raised the event</param>
        /// <param name="e">Details about the event</param>
        private void runQuery_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // Switch view back to normal
            View.EditorEnabled = true;

            // Set status text to read "Ready" and progress bar to 0 (which will hide it automatically)
            View.Status = "Ready";
            View.Progress = 0;
        }

        /// <summary>
        /// Instance of a main window presenter that this query tab presenter belongs to
        /// </summary>
        private readonly MainWindowPresenter m_MainWindowPresenter;

        /// <summary>
        /// The backround worker that runs our queries on another thread
        /// </summary>
        private BackgroundWorker m_RunQuery;
    }
}
